/* $Id: CategoryList.java 85 2009-01-02 01:39:50Z rmceoin $
 * 
 * Copyright 2008 Randy McEoin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bitsetters.android.passwordsafe;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;

/**
 * CategoryList Activity
 * 
 * @author Randy McEoin
 * @author Steven Osborn - http://steven.bitsetters.com
 */
public class CategoryList extends ListActivity {

	private static boolean debug = false;
	private static final String TAG = "CategoryList";

	// Menu Item order
	public static final int LOCK_CATEGORY_INDEX = Menu.FIRST;
	public static final int OPEN_CATEGORY_INDEX = Menu.FIRST + 1;
	public static final int EDIT_CATEGORY_INDEX = Menu.FIRST + 2;
	public static final int ADD_CATEGORY_INDEX = Menu.FIRST + 3;
	public static final int DEL_CATEGORY_INDEX = Menu.FIRST + 4;
	public static final int HELP_INDEX = Menu.FIRST + 5;
	public static final int CHANGE_PASS_INDEX = Menu.FIRST + 6;
	public static final int BACKUP_INDEX = Menu.FIRST + 7;
	public static final int RESTORE_INDEX = Menu.FIRST + 8;

	public static final int REQUEST_ONCREATE = 0;
	public static final int REQUEST_EDIT_CATEGORY = 1;
	public static final int REQUEST_ADD_CATEGORY = 2;
	public static final int REQUEST_OPEN_CATEGORY = 3;
	public static final int REQUEST_RESTORE = 4;

	protected static final int MSG_IMPORT = 0x101;
	protected static final int MSG_FILLDATA = MSG_IMPORT + 1;
	protected static final int MSG_BACKUP = MSG_FILLDATA + 1;

	private static final int IMPORT_PROGRESS_KEY = 0;
	private static final int BACKUP_PROGRESS_KEY = IMPORT_PROGRESS_KEY + 1;

	public static final int MAX_CATEGORIES = 256;

	private static final String EXPORT_FILENAME = "passwordsafe.csv";
	public static final String BACKUP_FILENAME = "passwordsafe.bak";

	public static final String KEY_ID = "id"; // Intent keys

	private CryptoHelper ch = null;
	private DBHelper dbHelper = null;

	private String importMessage = "";
	private int importedEntries = 0;
	private Thread importThread = null;
	private boolean importDeletedDatabase = false;

	private Thread backupThread = null;

	private static String masterKey;

	private static List<CategoryEntry> rows;

	BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
		public void onReceive(Context context, Intent intent) {
			if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
				if (debug)
					Log.d(TAG, "caught ACTION_SCREEN_OFF");
				masterKey = null;
			}
		}
	};

	public Handler myViewUpdateHandler = new Handler() {
		// @Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case CategoryList.MSG_BACKUP:
				Bundle b = msg.getData();
				String result = b.getString("msg");
				Toast.makeText(CategoryList.this, result, Toast.LENGTH_LONG)
						.show();
				break;
			case CategoryList.MSG_IMPORT:
				if (importMessage != "") {
					Toast.makeText(CategoryList.this, importMessage,
							Toast.LENGTH_LONG).show();
				}

				String deleteMsg = getString(R.string.import_delete_csv) + " "
						+ EXPORT_FILENAME + "?";
				Dialog about = new AlertDialog.Builder(CategoryList.this)
						.setIcon(R.drawable.passicon).setTitle(
								R.string.import_complete).setPositiveButton(
								R.string.yes,
								new DialogInterface.OnClickListener() {
									public void onClick(DialogInterface dialog,
											int whichButton) {
										File csvFile = new File(EXPORT_FILENAME);
										csvFile.delete();
									}
								}).setNegativeButton(R.string.no,
								new DialogInterface.OnClickListener() {
									public void onClick(DialogInterface dialog,
											int whichButton) {
									}
								}).setMessage(deleteMsg).create();
				about.show();

				if ((importedEntries != 0) || (importDeletedDatabase)) {
					fillData();
				}
				break;
			case CategoryList.MSG_FILLDATA:
				fillData();
				break;
			}
			super.handleMessage(msg);
		}
	};

	/**
	 * Called when the activity is first created.
	 */
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		if (debug)
			Log.d(TAG, "onCreate()");

		if (!isSignedIn()) {
			Intent frontdoor = new Intent(this, FrontDoor.class);
			startActivity(frontdoor);
			finish();
		}

		setContentView(R.layout.cat_list);
		String title = getResources().getString(R.string.app_name) + " - "
				+ getResources().getString(R.string.categories);
		setTitle(title);

		if (dbHelper == null) {
			dbHelper = new DBHelper(this);
			if (dbHelper.getPrePopulate() == true) {
				prePopulate();
				dbHelper.clearPrePopulate();
			}
		}

		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_SCREEN_OFF);
		registerReceiver(mIntentReceiver, filter);

		fillData();

		final ListView list = getListView();
		list.setFocusable(true);
		list.setOnCreateContextMenuListener(this);
		registerForContextMenu(list);
	}

	@Override
	protected void onResume() {
		super.onResume();

		if (debug)
			Log.d(TAG, "onResume()");
		if (dbHelper == null) {
			dbHelper = new DBHelper(this);
		}
		fillData();

		if (!isSignedIn()) {
			Intent frontdoor = new Intent(this, FrontDoor.class);
			startActivity(frontdoor);
			finish();
		}
	}

	@Override
	protected void onPause() {
		super.onPause();

		if (debug)
			Log.d(TAG, "onPause()");

		if ((importThread != null) && (importThread.isAlive())) {
			if (debug)
				Log.d(TAG, "wait for thread");
			int maxWaitToDie = 500000;
			try {
				importThread.join(maxWaitToDie);
			} catch (InterruptedException e) {
			} // ignore
		}
		if ((backupThread != null) && (backupThread.isAlive())) {
			if (debug)
				Log.d(TAG, "wait for backup thread");
			int maxWaitToDie = 500000;
			try {
				backupThread.join(maxWaitToDie);
			} catch (InterruptedException e) {
			} // ignore
		}
		dbHelper.close();
		dbHelper = null;
	}

	@Override
	public void onStop() {
		super.onStop();

		if (debug)
			Log.d(TAG, "onStop()");
		// dbHelper.close();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		unregisterReceiver(mIntentReceiver);
		if (debug)
			Log.d(TAG, "onDestroy()");
	}

	@Override
	public void onCreateContextMenu(ContextMenu menu, View view,
			ContextMenuInfo menuInfo) {

		AdapterView.AdapterContextMenuInfo info;
		info = (AdapterView.AdapterContextMenuInfo) menuInfo;

		menu.setHeaderTitle(rows.get(info.position).plainName);
		menu.add(0, OPEN_CATEGORY_INDEX, 0, R.string.open).setIcon(
				android.R.drawable.ic_menu_view).setAlphabeticShortcut('o');
		menu.add(0, EDIT_CATEGORY_INDEX, 0, R.string.password_edit).setIcon(
				android.R.drawable.ic_menu_edit).setAlphabeticShortcut('e');
		menu.add(0, DEL_CATEGORY_INDEX, 0, R.string.password_delete).setIcon(
				android.R.drawable.ic_menu_delete).setAlphabeticShortcut('d');
	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		onOptionsItemSelected(item);
		return true;
	}

	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case IMPORT_PROGRESS_KEY: {
			ProgressDialog dialog = new ProgressDialog(this);
			dialog.setMessage(getString(R.string.import_progress));
			dialog.setIndeterminate(false);
			dialog.setCancelable(false);
			return dialog;
		}
		case BACKUP_PROGRESS_KEY: {
			ProgressDialog dialog = new ProgressDialog(this);
			dialog.setMessage(getString(R.string.backup_progress) + " "
					+ BACKUP_FILENAME);
			dialog.setIndeterminate(false);
			dialog.setCancelable(false);
			return dialog;
		}
		}
		return null;
	}

	/**
	 * Returns the current status of signedIn.
	 * 
	 * @return True if signed in
	 */
	public static boolean isSignedIn() {
		if (masterKey != null) {
			return true;
		}
		return false;
	}

	/**
	 * Sets signedIn status to false.
	 * 
	 * @see com.bitsetters.android.passwordsafe.CategoryList#isSignedIn
	 */
	public static void setSignedOut() {
		if (debug)
			Log.d(TAG, "setSignedOut()");
		masterKey = null;
	}

	/**
	 * Populates the category ListView
	 */
	private void fillData() {
		if (debug)
			Log.d(TAG, "fillData()");
		// initialize crypto so that we can display readable descriptions in
		// the list view
		ch = new CryptoHelper();
		if (masterKey == null) {
			masterKey = "";
		}
		ch.setPassword(masterKey);

		List<String> items = new ArrayList<String>();
		if (dbHelper == null) {
			return;
		}
		rows = dbHelper.fetchAllCategoryRows();

		for (CategoryEntry row : rows) {
			String cryptDesc = row.name;
			Log.d(TAG, "Encrypted name " + row.name);
			row.plainName = "";
			try {
				row.plainName = ch.decrypt(cryptDesc);
				Log.d(TAG, "Decrypted name " + row.plainName);
			} catch (CryptoHelperException e) {
				Log.e(TAG, e.toString());
			}
		}
		
		for (CategoryEntry row : rows) {
			items.add(row.plainName);
		}

		ArrayAdapter<String> entries = new ArrayAdapter<String>(this,
				android.R.layout.simple_list_item_1, items);
		setListAdapter(entries);

	}

	@Override
	public boolean onMenuOpened(int featureId, Menu menu) {
		MenuItem miDelete = menu.findItem(DEL_CATEGORY_INDEX);
		MenuItem miEdit = menu.findItem(EDIT_CATEGORY_INDEX);
		if (getSelectedItemPosition() > -1) {
			miDelete.setEnabled(true);
			miEdit.setEnabled(true);
		} else {
			miDelete.setEnabled(false);
			miEdit.setEnabled(false);
		}
		return super.onMenuOpened(featureId, menu);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);

		menu.add(0, LOCK_CATEGORY_INDEX, 0, R.string.password_lock).setIcon(
				android.R.drawable.ic_lock_lock).setShortcut('0', 'l');
		menu.add(0, EDIT_CATEGORY_INDEX, 0, R.string.password_edit).setIcon(
				android.R.drawable.ic_menu_edit).setShortcut('1', 'e');
		menu.add(0, ADD_CATEGORY_INDEX, 0, R.string.password_add).setIcon(
				android.R.drawable.ic_menu_add).setShortcut('2', 'a');

		menu.add(0, DEL_CATEGORY_INDEX, 0, R.string.password_delete).setIcon(
				android.R.drawable.ic_menu_delete).setShortcut('3', 'd')
				.setEnabled(false);

		menu.add(0, HELP_INDEX, 0, R.string.help).setIcon(
				android.R.drawable.ic_menu_help);

		menu.add(0, CHANGE_PASS_INDEX, 0, R.string.change_password).setIcon(
				android.R.drawable.ic_menu_manage);

		menu.add(0, BACKUP_INDEX, 0, R.string.backup);
		menu.add(0, RESTORE_INDEX, 0, R.string.restore);

		return super.onCreateOptionsMenu(menu);
	}

	static void setMasterKey(String key) {
		masterKey = key;
	}

	static String getMasterKey() {
		return masterKey;
	}

	private void addCategory() {
		Intent i = new Intent(this, CategoryEdit.class);
		startActivityForResult(i, REQUEST_ADD_CATEGORY);
	}

	private void delCategory(long Id) {
		if (dbHelper.countPasswords(Id) > 0) {
			Toast.makeText(CategoryList.this, R.string.category_not_empty,
					Toast.LENGTH_SHORT).show();
			return;
		}
		dbHelper.deleteCategory(Id);
		fillData();
	}

	public boolean onOptionsItemSelected(MenuItem item) {

		AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
				.getMenuInfo();
		int position = -1;
		if (info == null) {
			position = getSelectedItemPosition();
		} else {
			// used when this is called from a ContextMenu
			position = info.position;
		}
		switch (item.getItemId()) {
		case LOCK_CATEGORY_INDEX:
			masterKey = null;
			Intent frontdoor = new Intent(this, FrontDoor.class);
			startActivity(frontdoor);
			finish();
			break;
		case OPEN_CATEGORY_INDEX:
			launchPassList(rows.get(info.position).id);
			break;
		case EDIT_CATEGORY_INDEX:
			Intent i = new Intent(this, CategoryEdit.class);
			if (position > -1) {
				i.putExtra(KEY_ID, rows.get(position).id);
				startActivityForResult(i, REQUEST_EDIT_CATEGORY);
			}
			break;
		case ADD_CATEGORY_INDEX:
			addCategory();
			break;
		case DEL_CATEGORY_INDEX:
			try {
				if (position > -1) {
					delCategory(rows.get(position).id);
				}
			} catch (IndexOutOfBoundsException e) {
				// This should only happen when there are no
				// entries to delete.
				Log.w(TAG, e.toString());
			}
			break;
		case HELP_INDEX:
			Intent help = new Intent(this, Help.class);
			startActivity(help);
			break;
		case CHANGE_PASS_INDEX:
			Intent changePass = new Intent(this, ChangePass.class);
			startActivity(changePass);
			break;
		case BACKUP_INDEX:
			backupThreadStart();
			break;
		case RESTORE_INDEX:
			restoreDatabase();
			break;
		default:
			Log.e(TAG, "Unknown itemId");
			break;
		}
		return super.onOptionsItemSelected(item);
	}

	private void launchPassList(long id) {
		Intent passList = new Intent(this, PassList.class);
		passList.putExtra(KEY_ID, id);
		startActivityForResult(passList, REQUEST_OPEN_CATEGORY);
	}

	private String backupDatabase() {
		Backup backup = new Backup(this);

		backup.write(this.getDir("data", 0) + "/" + BACKUP_FILENAME);
		return backup.getResult();
	}

	/**
	 * Start a separate thread to backup the database. By running the backup in
	 * a thread it allows the main UI thread to return and permit the updating
	 * of the progress dialog.
	 */
	private void backupThreadStart() {
		showDialog(BACKUP_PROGRESS_KEY);
		backupThread = new Thread(new Runnable() {
			public void run() {
				String result = backupDatabase();
				dismissDialog(BACKUP_PROGRESS_KEY);

				Message m = new Message();
				m.what = CategoryList.MSG_BACKUP;
				Bundle b = new Bundle();
				b.putString("msg", result);
				m.setData(b);
				CategoryList.this.myViewUpdateHandler.sendMessage(m);

				if (debug)
					Log.d(TAG, "thread end");
			}
		});
		backupThread.start();
	}

	private void restoreDatabase() {
		// Restore restore=new Restore(myViewUpdateHandler, this);

		// restore.read(BACKUP_FILENAME, masterKey);
		Intent i = new Intent(this, Restore.class);
		startActivityForResult(i, REQUEST_RESTORE);
	}

	protected void onListItemClick(ListView l, View v, int position, long id) {
		super.onListItemClick(l, v, position, id);

		launchPassList(rows.get(position).id);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent i) {
		super.onActivityResult(requestCode, resultCode, i);

		if (dbHelper == null) {
			dbHelper = new DBHelper(this);
		}

		if (resultCode == RESULT_OK) {
			fillData();
		}
	}

	private void prePopulate() {
		addCategory(getString(R.string.category_business));
		addCategory(getString(R.string.category_personal));
	}

	private long addCategory(String name) {
		if (debug)
			Log.d(TAG, "addCategory(" + name + ")");
		if ((name == null) || (name == ""))
			return -1;
		CategoryEntry entry = new CategoryEntry();

		String namePlain = name;
		Log.d(TAG, "name: " + namePlain);
		try {
			ch = new CryptoHelper();
			if (masterKey == null) {
				masterKey = "";
			}
			ch.setPassword(masterKey);

			entry.name = ch.encrypt(namePlain);
			Log.d(TAG, "encrypted name: " + entry.name);
		} catch (CryptoHelperException e) {
			Log.e(TAG, e.toString());
		}
		return dbHelper.saveCategory(entry);
	}

	private void deleteDatabaseNow() {
		dbHelper.deleteDatabase();
	}

	public static HashMap<String, Long> getCategoryToId(DBHelper dbHelper) {
		CryptoHelper ch = new CryptoHelper();
		if (masterKey == null) {
			masterKey = "";
		}
		ch.setPassword(masterKey);

		HashMap<String, Long> categories = new HashMap<String, Long>();
		rows = dbHelper.fetchAllCategoryRows();

		for (CategoryEntry row : rows) {
			String cryptDesc = row.name;
			row.plainName = "";
			try {
				row.plainName = ch.decrypt(cryptDesc);
				categories.put(row.plainName, row.id);
			} catch (CryptoHelperException e) {
				Log.e(TAG, e.toString());
			}
		}
		return categories;
	}
}